package io.kwdb.sdk;

import java.util.List;
import java.util.Map;

/**
 * InfluxDB Line Protocol encoder for KWDB. Converts Point objects to ILP format with proper
 * escaping.
 *
 * <p>Format: measurement,tag1=value1,tag2=value2 field1=value1,field2=value2 timestamp
 *
 * <p>Escaping rules: - Measurement/Tag/Field keys: escape spaces, commas, equals - Tag values:
 * escape spaces, commas, equals - String field values: wrap in quotes, escape quotes and
 * backslashes
 */
public class IlpEncoder {

  private final boolean ilpIntegerWithSuffix;

  /** Create encoder with default settings (integer suffix enabled). */
  public IlpEncoder() {
    this(true);
  }

  /**
   * Create encoder with specified settings.
   *
   * @param ilpIntegerWithSuffix Whether to append 'i' suffix to integer values
   */
  public IlpEncoder(boolean ilpIntegerWithSuffix) {
    this.ilpIntegerWithSuffix = ilpIntegerWithSuffix;
  }

  /**
   * Encode a single point to ILP format.
   *
   * @param point The point to encode
   * @return ILP formatted string with trailing newline
   */
  public String encode(Point point) {
    if (point == null) {
      throw new IllegalArgumentException("Point cannot be null");
    }

    StringBuilder sb = new StringBuilder();

    // Measurement
    sb.append(escapeMeasurement(point.getMeasurement()));

    // Tags (sorted for consistency)
    Map<String, String> tags = point.getTags();
    if (!tags.isEmpty()) {
      tags.entrySet().stream()
          .sorted(Map.Entry.comparingByKey())
          .forEach(
              entry -> {
                sb.append(',');
                sb.append(escapeTagKey(entry.getKey()));
                sb.append('=');
                sb.append(escapeTagValue(entry.getValue()));
              });
    }

    // Space separator between tags and fields
    sb.append(' ');

    // Fields
    Map<String, Object> fields = point.getFields();
    if (fields.isEmpty()) {
      throw new IllegalArgumentException("Point must have at least one field");
    }

    boolean firstField = true;
    for (Map.Entry<String, Object> entry : fields.entrySet()) {
      if (!firstField) {
        sb.append(',');
      }
      firstField = false;

      sb.append(escapeFieldKey(entry.getKey()));
      sb.append('=');
      sb.append(encodeFieldValue(entry.getValue()));
    }

    // Timestamp
    if (point.hasTimestamp()) {
      sb.append(' ').append(point.getTimestamp());
    }

    // Always add trailing newline for ILP protocol compliance
    sb.append('\n');

    return sb.toString();
  }

  /**
   * Encode multiple points to ILP format.
   *
   * @param points List of points to encode
   * @return ILP formatted string with one point per line, each ending with newline
   */
  public String encode(List<Point> points) {
    if (points == null || points.isEmpty()) {
      return "";
    }

    StringBuilder sb = new StringBuilder();
    for (Point point : points) {
      // Each encode() call already adds a trailing \n
      sb.append(encode(point));
    }
    return sb.toString();
  }

  /** Escape measurement name. Escapes: space, comma */
  private String escapeMeasurement(String str) {
    if (str == null || str.isEmpty()) {
      throw new IllegalArgumentException("Measurement cannot be null or empty");
    }
    return str.replace(" ", "\\ ").replace(",", "\\,");
  }

  /** Escape tag key. Escapes: space, comma, equals */
  private String escapeTagKey(String str) {
    if (str == null || str.isEmpty()) {
      throw new IllegalArgumentException("Tag key cannot be null or empty");
    }
    return str.replace(" ", "\\ ").replace(",", "\\,").replace("=", "\\=");
  }

  /** Escape tag value. Escapes: space, comma, equals */
  private String escapeTagValue(String str) {
    if (str == null) {
      return "";
    }
    return str.replace(" ", "\\ ").replace(",", "\\,").replace("=", "\\=");
  }

  /** Escape field key. Escapes: space, comma, equals */
  private String escapeFieldKey(String str) {
    if (str == null || str.isEmpty()) {
      throw new IllegalArgumentException("Field key cannot be null or empty");
    }
    return str.replace(" ", "\\ ").replace(",", "\\,").replace("=", "\\=");
  }

  /**
   * Encode field value based on type. - String: wrapped in quotes, escape quotes and backslashes -
   * Boolean: true/false - Integer/Long: with optional 'i' suffix - Float/Double: as-is
   */
  private String encodeFieldValue(Object value) {
    if (value == null) {
      return "null";
    }

    if (value instanceof String) {
      String str = (String) value;
      // Escape backslashes first, then quotes
      str = str.replace("\\", "\\\\").replace("\"", "\\\"");
      return "\"" + str + "\"";
    } else if (value instanceof Boolean) {
      return value.toString();
    } else if (value instanceof Long || value instanceof Integer) {
      String numStr = value.toString();
      return ilpIntegerWithSuffix ? numStr + "i" : numStr;
    } else if (value instanceof Float || value instanceof Double) {
      return value.toString();
    } else {
      // Fallback: treat as string
      return encodeFieldValue(value.toString());
    }
  }

  /**
   * Check if integer suffix is enabled.
   *
   * @return true if integers will have 'i' suffix
   */
  public boolean isIlpIntegerWithSuffix() {
    return ilpIntegerWithSuffix;
  }
}
